// perl_menu.cpp - menu and supporting classes

#include "PlPN.h"

PerlMenuItemTracker menu_item_tracker = PerlMenuItemTracker();

/*
 * PerlMenu
 */
 
PerlMenu::PerlMenu(const char* classname, extensions::cookie_t cookie) {
		m_cookie = cookie;
		CreatePerlObj(classname, (void*)this);
//		if (my_perl == NULL)
//			my_perl = get_my_perl();
}

PerlMenu::~PerlMenu() {}

int PerlMenu::GetItemCount() const {
	int ret;
	dSP;

    ENTER;
    SAVETMPS;

	PUSHMARK(SP);
	XPUSHs(m_perl_obj);
	PUTBACK;
	 
	int count = call_method("GetItemCount", G_SCALAR|G_EVAL);
	SPAGAIN;

	if(SvTRUE(ERRSV)) {
		STRLEN n_a;
		alert("Error calling Perl version of GetItemCount: %s\n", SvPV(ERRSV,n_a));
		POPs;
	}
	else if (count != 1) {
		alert("PerlMenu::GetItemCount did not return a value");
		ret = 0;
	}
	else {
		ret = POPi;
	}
	
	PUTBACK;
	FREETMPS;
	LEAVE;
	
	return ret;
}

const extensions::MenuItem& PerlMenu::GetItem(int index) const {
	extensions::MenuItem* item;
	SV* hashref = NULL;
	
	dSP;
	
	ENTER;
	SAVETMPS;
	
	PUSHMARK(SP);
	XPUSHs(m_perl_obj);
	XPUSHs(sv_2mortal(newSViv(index)));
	PUTBACK;
	
	int count = call_method("GetItem", G_SCALAR|G_EVAL);
	SPAGAIN;

	if(SvTRUE(ERRSV)) {
		STRLEN n_a;
		alert("Error calling Perl version of GetItem: %s\n", SvPV(ERRSV,n_a));
		POPs;
	}
	else if (count != 1) {
		croak("%s::GetItem(index = %d) did not return a value",
			/*HvNAME(SvSTASH(perl_obj))*/"PerlMenu", index);	// why is this crashing?
	}
	else {
		hashref = POPs;
		HV* hash = (HV*)SvRV(hashref);
		if (SvTYPE((SV*)hash) != SVt_PVHV ) {
			croak("%s::GetItem(index = %d) did not return a hashref",
				/*HvNAME(SvSTASH(perl_obj))*/"PerlMenu", index);	// why is this crashing?
		}
		SV** val;
		
		// UserData
		extensions::cookie_t cookie;
		val = hv_fetch(
			hash,	/* target */
			"id",	/* key */
			2,		/* key length */
			0		/* is this fetch part of a store? */
		);
		if (val == NULL) {
			croak("The hashref returned by %s::GetItem(index = %d) did not have an id element",
				/*HvNAME(SvSTASH(perl_obj))*/"PerlMenu", index);	// why is this crashing?
		}
		else {
			cookie = code_2_cookie(*val);
		}

		if (TrackedMenuItem* titem = menu_item_tracker.Fetch(cookie)) {
			item = titem->menuitem;
		}
		else {
			item = new extensions::MenuItem();
			item->UserData = cookie;
		}

		// we haven't seen this item before, so create a new one
		
		// Title
		val = hv_fetch(
			hash,	/* target */
			"text",	/* key */
			4,		/* key length */
			0		/* is this fetch part of a store? */
		);
		if (val == NULL) {
			croak("The hashref returned by %s::GetItem(index = %d) did not have a text element",
				/*HvNAME(SvSTASH(perl_obj))*/"PerlMenu", index);	// why is this crashing?
		}
		else {
			STRLEN len;
			SV* val_null = sv_mortalcopy(*val);
			sv_catpvn(val_null, "\0", 1);
			item->Title = utf8_2_wide(SvPV(val_null, len), len+1);
		}
		
		// if we have SubItems, add and set Type = miSubmenu
		val = hv_fetch(
			hash,		/* target */
			"submenu",	/* key */
			7,			/* key length */
			0			/* is this fetch part of a store? */
		);
		if (val == NULL) {
			// no SubItems, default to Handler
			// (the handler function will emit a warning if no handler is set)
			item->Type = extensions::miItem;
			item->Handler = PerlMenuHandler;
		}
		else {
			item->Type = extensions::miSubmenu;
			item->SubItems = (extensions::IMenuItems*)retrieve_cpp_object(*val);
		}
		menu_item_tracker.Add(item, hashref);
	}
	
	PUTBACK;
	FREETMPS;
	LEAVE;
	
	return *item;
}

extensions::cookie_t PerlMenu::GetCookie() {
	return m_cookie;
}

/*
 * PerlMenuTracker
 */

PerlMenuTracker::PerlMenuTracker() {}

PerlMenuTracker::~PerlMenuTracker() {
	for (int i = 0; i < m_tracked_menus.size(); i++) {
		delete m_tracked_menus[i]->menu;
	}
}

// add the menu; return true if added, false if already present
bool PerlMenuTracker::Add(PerlMenu* menu) {
	extensions::cookie_t cookie = menu->GetCookie();
	if (Fetch(cookie) == NULL) {
		// we don't have it yet, so add it
		TrackedMenu* tmenu = new TrackedMenu;
		tmenu->cookie = cookie;
		tmenu->menu = menu;
		m_tracked_menus.push_back(tmenu);
		// PN will call menu->GetItem for each item
		// so we don't need to
		return true;
	}
	else {
		// we've already got it, update the items
		int size = menu->GetItemCount();
		for (int i = 0; i < size; i++) {
			// menu->GetItem will convert the perl storage to a MenuItem
			// and add it to the tracker if necessary
			menu->GetItem(i);
		}
		return false;
	}
}

TrackedMenu* PerlMenuTracker::Fetch(extensions::cookie_t cookie) {
	for (int i = 0; i < m_tracked_menus.size(); i++) {
		if (m_tracked_menus[i]->cookie == cookie)
			return m_tracked_menus[i];
	}
	return NULL;
}

void PerlMenuTracker::Blank() {
	menu_item_tracker.Blank();
}

/*
 * PerlMenuItemTracker
 */
 
PerlMenuItemTracker::PerlMenuItemTracker() {}
 
PerlMenuItemTracker::~PerlMenuItemTracker() {
	for (int i = 0; i < m_tracked_menu_items.size(); i++) {
		delete m_tracked_menu_items[i]->menuitem;
		if (m_tracked_menu_items[i]->hashref != NULL) {
			// if we're not in global destruction, decrement the refcount
			if (! PL_dirty)
				SvREFCNT_dec((SV*)m_tracked_menu_items[i]->hashref);
			m_tracked_menu_items[i]->hashref = NULL;
		}
		// Perl will garbage collect the hashref
	}
}

// add or update the item; return true if added, false if merely updated
bool PerlMenuItemTracker::Add(extensions::MenuItem* menuitem, SV* hashref) {
	TrackedMenuItem* item = Fetch(menuitem->UserData);
	if (item == NULL) {
		// we don't have it yet, so add it
		item = new TrackedMenuItem();
		item->menuitem = menuitem;
		item->hashref = hashref;
		m_tracked_menu_items.push_back(item);
		SvREFCNT_inc((SV*)hashref);
		return true;
	}
	else {
		// we already have it, update existing
		if (item->hashref != NULL)
			SvREFCNT_dec((SV*)item->hashref);
		SvREFCNT_inc((SV*)hashref);
		item->hashref = hashref;
		
		// update submenuitems (if any)
		if (menuitem->Type == extensions::miSubmenu) {
			int size = menuitem->SubItems->GetItemCount();
			for (int i = 0; i < size; i++) {
				// menu->GetItem will convert the perl storage to a MenuItem
				// and add it to the tracker if necessary
				menuitem->SubItems->GetItem(i);
			}
		}

		return false;
	}
}

TrackedMenuItem* PerlMenuItemTracker::Fetch(extensions::cookie_t cookie) {
	for (int i = 0; i < m_tracked_menu_items.size(); i++) {
		if (m_tracked_menu_items[i]->menuitem->UserData == cookie) {
			return m_tracked_menu_items[i];
		}
	}
	return NULL;
}

void PerlMenuItemTracker::Blank() {
	for (int i = 0; i < m_tracked_menu_items.size(); i++) {
		if (m_tracked_menu_items[i]->hashref != NULL) {
			// if we're not in global destruction, decrement the refcount
			if (! PL_dirty)
				SvREFCNT_dec((SV*)m_tracked_menu_items[i]->hashref);
			m_tracked_menu_items[i]->hashref = NULL;
		}
	}
}

/*
 * PerlMenuHandler
 */

void PerlMenuHandler(extensions::cookie_t cookie) {
	TrackedMenuItem* item =	menu_item_tracker.Fetch(cookie);
	if (item == NULL || item->hashref == NULL)
		alert("Menu entry removed");
	else {
		// assume handler
		bool method = false;
		HV* hash = (HV*)SvRV(item->hashref);
		SV** val;	// for holding return value from hash fetch
		
		// check for title
		char* menu_title = "[No name found]";
		val = hv_fetch(
			hash,	/* target */
			"text",	/* key */
			4,		/* key length */
			0		/* is this fetch part of a store? */
		);
		if (val != NULL) {
			SV* val_null = sv_mortalcopy(*val);
			sv_catpvn(val_null, "\0", 1);
			menu_title = SvPV_nolen(val_null);
		}

		// check for handler
		val = hv_fetch(
			hash,		/* target */
			"handler",	/* key */
			7,			/* key length */
			0			/* is this fetch part of a store? */
		);
		// no handler found; check for method
		if (val == NULL) {
			method = true;
			val = hv_fetch(
				hash,		/* target */
				"method",	/* key */
				6,			/* key length */
				0			/* is this fetch part of a store? */
			);
		}

		// if we found a handler or a method
		if (val != NULL) {
			dSP;
			
			ENTER;
			SAVETMPS;
			
			PUSHMARK(SP);
			XPUSHs(item->hashref);
			XPUSHs(sv_2mortal(cookie_2_code(cookie)));
			PUTBACK;

			if (method) {
				SV* val_null = sv_mortalcopy(*val);
				sv_catpvn(val_null, "\0", 1);
				call_method(SvPV_nolen(val_null), G_VOID|G_EVAL);
			}
			else {
				call_sv(*val, G_VOID|G_EVAL);
			}

			SPAGAIN;
			if(SvTRUE(ERRSV)) {
				STRLEN n_a;
				alert("Error calling Perl menu handler for %s: %s\n", menu_title, SvPV(ERRSV,n_a));
			}

			PUTBACK;
			FREETMPS;
			LEAVE;
		}
		// no handler or method found
		else {
			alert("No menu handler found");
		}
	}
}
